package com.ease.util {

   import flash.utils.Dictionary;

   /**
    * @private
    *
    */
   public class MruCache implements ICache
   {
      private var _map:Dictionary = new Dictionary();
      private var _count:int;
      private var _capacity:int;
      private var _front:Item;
      private var _back:Item;

      public function MruCache( capacity:int ) {
         Diagnostics.assert( capacity > 0, "Cache capacity should be positive" );
         _capacity = (capacity > 0)? capacity : 1;
      }

      /** @inheritDoc */
      public function add( key:Object, value:Object ):void {
         var item:Item = _map[key] as Item;
         if (item) {
            // the key is already present; value could be the same or different
            item.value = value;
            moveToFront( item );
            return;
         }
         // The size of the cache is about to increase, so check against the capacity. The
         // removal needs to happen before insertion.
         if (_count >= _capacity) {
            removeFromBack();
         }
         addToFront( key, value );
      }

      /** @inheritDoc */
      public function getValue( key:Object ):* {
         var item:Item = _map[key];
         if (! item) {
            return null;
         }
         moveToFront( item );
         return item.value;
      }

      /** @inheritDoc */
      public function remove(key:Object):Object {
         var item:Item = _map[key];
         if (item == null) {
            return null;
         }

         removeWithoutDeleting(item);
         delete _map[key];
         _count--;
         return item.value;
      }

      /**
       * Removes all the elements from the cache.
       */
      public function removeAll():void {
         //re-initialize the dictionary
         _map = new Dictionary();
         _count = 0;
         _front = null;
         _back = null;
      }

      /**
       * Returns the cached objects keys.
       */
      public function get keys():Array {
         return MixedUtil.getKeys(_map);
      }

      public function get values():Array {
         return MixedUtil.getValues(_map);
      }

      /**
       * The count of items in this cache.
       */
      public function get count():int {
         return _count;
      }

      /**
       * @see ProfilingManager.getObjectSize.
       */
      public function getNestedSize():int {
         return ProfilingManager.instance.getObjectSize(_map);
      }

      private function addToFront( key:Object, value:Object ):void {
         if (! key) {
            return;
         }
         var item:Item = new Item( value );
         item.key = key;
         _map[key] = item;
         insertAtFront( item );
         if (! _back) {
            // got a new back
            _back = item;
         }
         _count++;
      }

      private function moveToFront( item:Item ):void {
         if (! item || item == _front) {
            return;
         }
         removeWithoutDeleting( item );
         insertAtFront( item );
      }

      /**
       * Insert the given item at the front
       *
       * @param item
       *  The item to be inserted
       */
      private function insertAtFront( item:Item ):void {
         item.next = null;
         item.prev = _front;
         if (_front) {
            _front.next = item;
         }
         _front = item;
      }

      /**
       * Kicks the item out from its current location
       *
       * @param item
       *  The item to be removed
       */
      private function removeWithoutDeleting( item:Item ):void {
         if (! item) {
            return;
         }
         if (item.next) {
            item.next.prev = item.prev;
         }
         if (item.prev) {
            item.prev.next = item.next;
         }
         if (item == _back) {
            _back = item.next; // next, because prev is null
         }
         if (item == _front) {
            _front = item.prev;
         }
      }

      /**
       * Removes the last item (i.e. the least accessed item) from the cache
       */
      private function removeFromBack():void {
         remove( _back.key );
      }
   } // class

}


import com.ease.util.ProfilingManager;

class Item {
   public var key:Object;
   public var value:Object;
   public var prev:Item;
   public var next:Item;

   public function Item( value:Object ) {
      this.value = value;
   }

   /**
    * @see ProfilingManager.getObjectSize.
    */
   public function getNestedSize():int {
      return ProfilingManager.instance.getObjectSize(this.value);
   }
}
